﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace YouVisio.Silverlight.TextDiff
{
    public class DiffAnalyser : IDiffAnalyser
    {

        public DiffPartsList Analyse<TSource, TPart>(
                                    IEqualityComparer<TPart> comparer,
                                    IPartsRetriever<TSource, TPart> before,
                                    IPartsRetriever<TSource, TPart> after,
                                    int beforeStartIndex,
                                    int afterStartIndex,
                                    int length)
        {
            Contract.Ensures(comparer != null, "comparer not found");
            Contract.Ensures(before != null, "before IDiffPartsRetriever not found");
            Contract.Ensures(after != null, "after IDiffPartsRetriever not found");

            if (beforeStartIndex >= before.Length || afterStartIndex >= after.Length || length <= 0)
                return DiffPartsList.Empty;

            if (beforeStartIndex < -1) beforeStartIndex = -1;
            if (afterStartIndex < -1) afterStartIndex = -1;

            var state = new PartsAggregationState<DiffPartsList, TSource, TPart>
            {
                Accumulator = new DiffPartsList(),
                Before = before,
                After = after,
                Comparer = comparer ?? EqualityComparer<TPart>.Default,
                InitialBeforeIndex = beforeStartIndex,
                InitialAfterIndex = afterStartIndex,
                Length = length,
                Function = OnAnalyseMatchingLines
            };

            state = AggregateParts(state);
            if (state.MaxBeforeIndex != state.LastMatchBeforeIndex || state.MaxAfterIndex != state.LastMatchAfterIndex)
            {
                AddToList(state.Accumulator, state.MaxBeforeIndex, state.LastMatchBeforeIndex, state.MaxAfterIndex, state.LastMatchAfterIndex, false);
            }
            return state.Accumulator;
        }

        private static PartsAggregationInfo<DiffPartsList, TSource, TPart> OnAnalyseMatchingLines<TSource, TPart>(PartsAggregationInfo<DiffPartsList, TSource, TPart> state)
        {
            AddToList(state.Accumulator, state.CurrentBeforeIndex, state.LastMatchBeforeIndex, state.CurrentAfterIndex, state.LastMatchAfterIndex, true);
            return state;
        }

        private static void AddToList(DiffPartsList list, int beforeNow, int beforeLast, int afterNow, int afterLast, bool nowIsMatch)
        {
            var deleted = beforeNow - beforeLast;
            var inserted = afterNow - afterLast;
            if (nowIsMatch)
            {
                if (deleted == 0 || inserted == 0) return;
                deleted--;
                inserted--;
            }
            if (deleted < 0 || inserted < 0) return;
            var same = Math.Min(deleted, inserted);
            if (same > 0)
            {
                list.Add(Operation.Change, same);
            }
            list.Add(Operation.Delete, deleted - same);
            list.Add(Operation.Insert, inserted - same);
            if (nowIsMatch)
            {
                list.Add(Operation.Equal);
            }
        }

        public PartsAggregationState<TAccumulator, TSource, TPart> AggregateParts<TAccumulator, TSource, TPart>(PartsAggregationState<TAccumulator, TSource, TPart> state)
        {
            state.VerifyInitialization();

            state.CurrentBeforeIndex = state.LastMatchBeforeIndex = state.InitialBeforeIndex;
            state.CurrentAfterIndex = state.LastMatchAfterIndex = state.InitialAfterIndex;

            var maxBeforeByLength = (Int32.MaxValue - (state.Length + state.InitialBeforeIndex)) <= 0 ? Int32.MaxValue : state.Length + state.InitialBeforeIndex;
            var maxAfterByLength = (Int32.MaxValue - (state.Length + state.InitialAfterIndex)) <= 0 ? Int32.MaxValue : state.Length + state.InitialAfterIndex;
            state.MaxBeforeIndex = Math.Min(state.Before.Length - 1, maxBeforeByLength);
            state.MaxAfterIndex = Math.Min(state.After.Length - 1, maxAfterByLength);

            var max = DateTime.Now.AddSeconds(10);
            var x = 0;
            while (state.Before.IndexIsWithin(state.CurrentBeforeIndex + 1) && state.After.IndexIsWithin(state.CurrentAfterIndex + 1))
            {
                while (true)
                {
                    state.CurrentBeforeIndex++;
                    state.CurrentAfterIndex++;

                    if (++x % 100 == 0 && DateTime.Now >= max)
                        throw new InvalidOperationException("Potential endless loop detected");

                    var exit = true;
                    if (state.CurrentAfterIndex <= state.MaxAfterIndex)
                    {
                        state.AfterValue = state.After.GetByIndex(state.CurrentAfterIndex);
                        var lookUpRow = state.SkipToCheckIfPartExists ? true : state.Before.Contains(state.AfterValue);
                        if (lookUpRow)
                        {
                            var preLoopCurrentBeforeIndex = state.CurrentBeforeIndex;
                            state.CurrentBeforeIndex = Math.Min(state.MaxBeforeIndex, state.CurrentBeforeIndex);
                            var foundMatch = false;
                            for (; state.CurrentBeforeIndex > state.LastMatchBeforeIndex; state.CurrentBeforeIndex--)
                            {
                                state.BeforeValue = state.Before.GetByIndex(state.CurrentBeforeIndex);
                                state.AreEqual = state.Comparer.Equals(state.BeforeValue, state.AfterValue);
                                state.Log(state);
                                if (state.AreEqual)
                                {
                                    var info = state.Function(new PartsAggregationInfo<TAccumulator, TSource, TPart>(state));
                                    state.Accumulator = info.Accumulator;
                                    state.LastMatchBeforeIndex = state.CurrentBeforeIndex;
                                    state.LastMatchAfterIndex = state.CurrentAfterIndex;
                                    foundMatch = true;
                                    break;
                                }
                            }
                            if (foundMatch) break;
                            state.CurrentBeforeIndex = preLoopCurrentBeforeIndex;
                        }
                        exit = false;
                    }
                    if (state.CurrentBeforeIndex <= state.MaxBeforeIndex)
                    {
                        state.BeforeValue = state.Before.GetByIndex(state.CurrentBeforeIndex);
                        var lookUpCol = state.SkipToCheckIfPartExists ? true : state.After.Contains(state.BeforeValue);
                        if (lookUpCol)
                        {
                            var preLoopCurrentAfterIndex = state.CurrentAfterIndex;
                            state.CurrentAfterIndex = Math.Min(state.MaxAfterIndex, state.CurrentAfterIndex - 1);
                            var foundMatch = false;
                            for (; state.CurrentAfterIndex > state.LastMatchAfterIndex; state.CurrentAfterIndex--)
                            {
                                state.AfterValue = state.After.GetByIndex(state.CurrentAfterIndex);
                                state.AreEqual = state.Comparer.Equals(state.BeforeValue, state.AfterValue);
                                state.Log(state);
                                if (state.AreEqual)
                                {
                                    var info =
                                        state.Function(new PartsAggregationInfo<TAccumulator, TSource, TPart>(state));
                                    state.Accumulator = info.Accumulator;
                                    state.LastMatchBeforeIndex = state.CurrentBeforeIndex;
                                    state.LastMatchAfterIndex = state.CurrentAfterIndex;
                                    foundMatch = true;
                                    break;
                                }
                            }
                            if (foundMatch) break;
                            state.CurrentAfterIndex = preLoopCurrentAfterIndex;
                        }
                        exit = false;
                    }
                    if (exit) return state;
                }
            }

            return state;
        }



    }
}
